/*
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA  02110-1301, USA.
 *
 * http://www.gnu.org/copyleft/gpl.html
 */
package system.ai;

import java.util.List;

import com.aionemu.commons.utils.Rnd;
import com.aionemu.gameserver.ai.AIClasss;
import com.aionemu.gameserver.ai.CreatureAI;
import com.aionemu.gameserver.ai.CtrlIntention;
import com.aionemu.gameserver.dataholders.DataManager;
import com.aionemu.gameserver.geoEngine.GeoEngine;
import com.aionemu.gameserver.model.EmotionType;
import com.aionemu.gameserver.model.gameobjects.AionObject;
import com.aionemu.gameserver.model.gameobjects.Creature;
import com.aionemu.gameserver.model.gameobjects.Npc;
import com.aionemu.gameserver.model.gameobjects.VisibleObject;
import com.aionemu.gameserver.model.gameobjects.instance.Monster;
import com.aionemu.gameserver.model.gameobjects.state.CreatureState;
import com.aionemu.gameserver.model.templates.npcTemplate.CreatureTemplate;
import com.aionemu.gameserver.model.templates.npcskill.NpcSkillList;
import com.aionemu.gameserver.model.templates.npcskill.NpcSkillTemplate;
import com.aionemu.gameserver.network.aion.serverpackets.SM_ATTACK_STATUS.TYPE;
import com.aionemu.gameserver.network.aion.serverpackets.SM_EMOTION;
import com.aionemu.gameserver.skillengine.SkillEngine;
import com.aionemu.gameserver.skillengine.model.Skill;
import com.aionemu.gameserver.utils.MathUtil;
import com.aionemu.gameserver.utils.PacketSendUtility;
import com.aionemu.gameserver.utils.gametime.GameTimeManager;
import com.aionemu.gameserver.world.World;

/**
 * @author Mr. Poke
 *
 */
public class NpcAI extends CreatureAI
{

	private volatile boolean	thinking	= false;
	private static final int MAX_ATTACK_TIMEOUT = 300;
	private static final int RANDOM_WALK_RANGE = 5;
	private static final int RANDOM_WALK_RATE = 30; // ~ 3 sec
	/** The delay after which the attacked is stopped */
	private int	attackTimeout;

	private volatile boolean isCastingSkill = false;
	/**
	 * @param owner
	 */
	public NpcAI(Creature owner)
	{
		super(owner);
	}
	
	/* (non-Javadoc)
	 * @see com.aionemu.gameserver.ai.CreatureAI#getOwner()
	 */
	@Override
	public Npc getOwner()
	{
		return (Npc)super.getOwner();
	}

	@Override
	protected void onIntentionAttack(Creature target)
	{
		attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeManager.getGameTicks();
		
		if (!owner.isInState(CreatureState.COMBAT))
		{
			owner.setTarget(null);
			owner.setState(CreatureState.COMBAT);
			owner.unsetState(CreatureState.WALKING);
			PacketSendUtility.broadcastPacket(owner, new SM_EMOTION(owner, EmotionType.START_EMOTE2, 0, target.getObjectId()));
			PacketSendUtility.broadcastPacket(owner, new SM_EMOTION(owner, EmotionType.ATTACKMODE, 0, target.getObjectId()));
		}
		super.onIntentionAttack(target);
	}

	@Override
	protected synchronized void changeIntention(CtrlIntention intention, Object arg0, Object arg1)
	{

		if (getOwner().getLifeStats().isAlreadyDead() || intention == CtrlIntention.IDLE)
		{
			super.changeIntention(CtrlIntention.IDLE, null, null);
			stopAITask();
			return;
		}
		super.changeIntention(intention, arg0, arg1);

		startAITask();
	}

	@Override
	protected void onEvtAttacked(Creature attacker)
	{
		attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeManager.getGameTicks();
		// Set the Intention to AI_INTENTION_ATTACK
		if (getIntention() != CtrlIntention.ATTACK)
		{
			setIntention(CtrlIntention.ATTACK, attacker);
		}
		else if (getOwner().getAggroList().getMostHated() != getAttackTarget())
		{
			setIntention(CtrlIntention.ATTACK, attacker);
		}

		super.onEvtAttacked(attacker);
	}

	@Override
	protected void onEvtDead()
	{
		changeIntention(CtrlIntention.IDLE, null, null);
		super.onEvtDead();
	}
	
	/**
	 * Launch actions corresponding to the Event ReadyToAct.<BR><BR>
	 *
	 * <B><U> Actions</U> :</B><BR><BR>
	 * <li>Launch actions corresponding to the Event Think</li><BR><BR>
	 *
	 */
	@Override
	protected void onEvtReadyToAct()
	{
		executeNextIntention();
	}

	@Override
	protected void onEvtFinishCasting()
	{
		super.onEvtFinishCasting();
		isCastingSkill = false;
		executeNextIntention();
	}

	/**
	 * Manage AI thinking actions of a Creature.<BR><BR>
	 */
	@Override
	protected void onEvtThink()
	{
		// Check if the actor can't use skills and if a thinking action isn't already in progress
		if (thinking || getOwner().isAllSkillsDisabled())
			return;

		if (!getOwner().isSpawned() || getOwner().getKnownList().getKnownPlayers().isEmpty())
		{
			changeIntention(CtrlIntention.IDLE, null, null);
			return;
		}
		// Start thinking action
		thinking = true;

		try
		{
			// Manage AI thinks of a L2Attackable
			if (getIntention() == CtrlIntention.ACTIVE)
				thinkActive();
			else if (getIntention() == CtrlIntention.ATTACK)
				thinkAttack();
		}
		finally
		{
			// Stop thinking action
			thinking = false;
		}
	}

	/* (non-Javadoc)
	 * @see com.aionemu.gameserver.ai.CreatureAI#onEvtForgetObject(com.aionemu.gameserver.model.gameobjects.VisibleObject)
	 */
	@Override
	protected void onEvtForgetObject(VisibleObject object)
	{
		super.onEvtForgetObject(object);
		if (object == this.followTarget)
			this.stopFollow();
		if (object == getAttackTarget() && getIntention() == CtrlIntention.ATTACK)
			this.setAttackTarget(null);
	}

	/**
	 * 
	 */
	private void thinkAttack()
	{
		if (getAttackEndEvtReadyToAct().isScheduled() || isCastingSkill)
			return;
		Creature originalAttackTarget = getAttackTarget();
		// Check if target is dead or if timeout is expired to stop this attack
		if (originalAttackTarget == null || originalAttackTarget.getLifeStats().isAlreadyDead() || attackTimeout < GameTimeManager.getGameTicks())
		{
			// Stop hating this target after the attack timeout or if target is dead
			if (originalAttackTarget != null)
			{
				owner.setTarget(null);
				setAttackTarget(null);
				getOwner().getAggroList().remove(originalAttackTarget);
			}

			// Set the AI Intention to AI_INTENTION_ACTIVE
			setIntention(CtrlIntention.ACTIVE);

			return;
		}
		Creature MostHate = getOwner().getAggroList().getMostHated();
		if (MostHate == null)
		{
			owner.setTarget(null);
			setAttackTarget(null);
			setIntention(CtrlIntention.ACTIVE);
			return;
		}
		if (MostHate != getAttackTarget())
		{
			setAttackTarget(MostHate);
			owner.setTarget(MostHate);
		}

		float attackRange = getOwner().getBoundingRadius().getCollision()+MostHate.getBoundingRadius().getCollision()+(getOwner().getGameStats().getAttackRange().getCurrent()/1000f);
		float range = (float) MathUtil.getDistance(owner, MostHate);
		NpcSkillList npcSkills = DataManager.NPC_SKILL_DATA.getNpcSkillList(getOwner().getNpcId());
		if (npcSkills != null && Rnd.get(25) == 0 && !npcSkills.getNpcSkills().isEmpty())
		{
			List<NpcSkillTemplate> skills = npcSkills.getNpcSkills();
			int skillId = skills.get(Rnd.get(0, skills.size()-1)).getSkillid();
			Skill skill = SkillEngine.getInstance().getSkill(getOwner(), skillId, 1, MostHate);
			if (skill != null)
			{
				if (skill.useSkill())
				{
					if (skill.getSkillTemplate().getDuration() > 0)
						isCastingSkill = true;
					return;
				}
			}
		}
		if (range > attackRange || !GeoEngine.getInstance().canSee(getOwner(), MostHate))
		{
			moveToPawn(MostHate, attackRange-0.1f);
		}
		else if (!getAttackReuseEndEvtReadyToAct().isScheduled())
		{
			if (attackTarget())
				attackTimeout = MAX_ATTACK_TIMEOUT + GameTimeManager.getGameTicks();
		}
	}

	/**
	 * 
	 */
	protected void thinkActive()
	{
		Creature mostHate = getOwner().getAggroList().getMostHated();
		if (mostHate != null && MathUtil.getDistance(owner, mostHate) < 50 && !mostHate.getLifeStats().isAlreadyDead())
		{
			setIntention(CtrlIntention.ATTACK, mostHate);
			return;
		}
		for (Creature creature : getOwner().getKnownList().getKnownAgressiveTo().values())
		{
			if ((!getOwner().isAggroTo(creature))|| creature.getLifeStats().isAlreadyDead())
				continue;
			float range = (float) MathUtil.getDistance(creature, owner)-getOwner().getBoundingRadius().getCollision()-creature.getBoundingRadius().getCollision();
			if (range > ((CreatureTemplate)getOwner().getObjectTemplate()).getCreatureStatData().getSensoryRange())
				continue;
			if (!GeoEngine.getInstance().canSee(creature, owner))
				continue;
			owner.setTarget(creature);
			getOwner().getAggroList().addHate(creature, 1);
			return;
		}

		if (owner.isInState(CreatureState.COMBAT))
		{
			owner.unsetState(CreatureState.COMBAT);
			owner.setState(CreatureState.WALKING);
			PacketSendUtility.broadcastPacket(owner, new SM_EMOTION(owner, EmotionType.START_EMOTE2, 0, 0));
			PacketSendUtility.broadcastPacket(owner, new SM_EMOTION(owner, EmotionType.NEUTRALMODE, 0, 0));
		}

		if (!getOwner().getLifeStats().isFullyRestoredHp())
		{
			getOwner().getLifeStats().increaseHp(TYPE.NATURAL_HP, getOwner().getLifeStats().getMaxHp()/5);
			return;
		}

		if (getOwner().getSpawn() != null)
		{
			double dist = MathUtil.getDistance(owner, getOwner().getSpawn().getX(), getOwner().getSpawn().getY(), getOwner().getSpawn().getZ());
			if (dist > RANDOM_WALK_RANGE)
			{
				getOwner().onMoveTo(getOwner().getSpawn().getX(), getOwner().getSpawn().getY(), getOwner().getSpawn().getZ());
				return;
			}
			
			if (getOwner() instanceof Monster)
			{
				if (Rnd.nextInt(RANDOM_WALK_RATE) == 0)
				{
					float x  = Rnd.nextInt(RANDOM_WALK_RANGE* 2) - RANDOM_WALK_RANGE;
					float y  = Rnd.nextInt(RANDOM_WALK_RANGE* 2) - RANDOM_WALK_RANGE;
					getOwner().onMoveTo(getOwner().getSpawn().getX()+x, getOwner().getSpawn().getY()+y, getOwner().getSpawn().getZ());
					return;
				}
			}
		}
		if (owner.getKnownList().getKnownPlayers().isEmpty())
		{
			changeIntention(CtrlIntention.IDLE, null, null);
			return;
		}
	}

	public static void main(String[] args)
	{
		AIClasss.getInstance().addClass("NpcAI", NpcAI.class);
		for (AionObject object : World.getInstance().getAllObjects())
		{
			if (object instanceof Monster)
			{
				((Monster)object).initializeAi();
			}
		}
	}
}
